#ifndef __M_BINDING_H__
#define __M_BINDING_H__
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <cassert>
#include <sstream>

namespace mymq
{
    struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string exchange_name;
        std::string msgqueue_name;
        std::string binding_key;

        Binding(){}
        Binding(const std::string& ename, const std::string& qname, const std::string& key)
        : exchange_name(ename), msgqueue_name(qname), binding_key(key)
        {}
    };

    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;
    class BindingMapper
    {
    public:
        BindingMapper(const std::string& dbfile)
        : _sql_helper(dbfile)
        {
            // 确保创建数据库之前，父级目录存在
            std::string par_path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(par_path);
            assert(_sql_helper.open()); // 打开数据库，没有就创建
            createTable();
        }

        void createTable()
        {
            // "create table if not exists binding_table(exchange_name varchar(32), msgqueue_name, binding_key)"
            std::stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32), ";
            sql << "msgqueue_name varchar(32), ";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }

        void removeTable()
        {
            // drop table if exists binding_table
            std::string sql = "drop table if exists binding_table;";
            assert(_sql_helper.exec(sql, nullptr, nullptr));
        }

        bool insert(Binding::ptr& binding)
        {
            // insert into binding_table values('ename', 'qname', 'key');
            std::stringstream sql;
            sql << "insert into binding_table values(";
            sql << "'" << binding->exchange_name << "', ";
            sql << "'" << binding->msgqueue_name << "', ";
            sql << "'" << binding->binding_key << "');";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void remove(const std::string& ename, const std::string& qname)
        {
            // delete from binding_table where exchange_name='ename' and msgqueue_name='qname'
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "' and ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void removeExchangeBindings(const std::string& ename)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        void removeMsgQueueBindings(const std::string& qname)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }

        BindingMap recovery()
        {
            BindingMap result;
            std::string sql = "select exchange_name, msgqueue_name, binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallBack, &result);
            return result;
        }
    private:
        static int selectCallBack(void* args, int numcol, char** row, char** fields)
        {
            BindingMap* result = (BindingMap*)args;
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);
            MsgQueueBindingMap& qmap = (*result)[bp->exchange_name];
            qmap.insert(std::make_pair(bp->msgqueue_name, bp)); 
            return 0;
        }
    private:
        SqliteHelper _sql_helper;
    };

    class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string& dbfile)
        : _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }

        bool bind(const std::string& ename, const std::string& qname, const std::string& key, bool durable)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            // 如果绑定关系存在就OK，没有就创建
            auto eit = _bindings.find(ename);
            if (eit != _bindings.end())
            {
                // 存在交换机相关信息
                auto qit = eit->second.find(qname);
                if (qit != eit->second.end()) // 交换机存在相关队列绑定信息
                    return true;
            }

            // 创建绑定信息，如何判断是否需要持久化呢？只有在交换机和队列都需要持久化时，绑定才会有持久化的需求
            // 但是为了模块之间的降低耦合度，直接设置一个参数即可，让上层去考虑需不需要持久化
            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
            if (durable)
            {
                bool ret = _mapper.insert(bp);
                if (ret == false) return false; // 持久化失败
            }
            MsgQueueBindingMap& qmap = _bindings[ename];
            qmap.insert(std::make_pair(qname, bp));
            return true;
        }

        void unBind(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            auto eit = _bindings.find(ename);
            if (eit == _bindings.end()) return; // 没有交换机相关绑定信息
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end()) return; // 交换机没有队列的绑定信息
            
            _mapper.remove(ename, qname);
            _bindings[ename].erase(qname);
        }

        void removeExchangeBindings(const std::string& ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 不判断存储不存在了，虽然效率低一些，但是代码简洁
            _mapper.removeExchangeBindings(ename);
            _bindings.erase(ename);
        }

        void removeMsgQueueBindings(const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeMsgQueueBindings(qname);
            for (auto it = _bindings.begin(); it != _bindings.end(); ++it)
            {
                // 遍历每个交换机，删除指定队列的绑定信息
                it->second.erase(qname);
            }
        }

        MsgQueueBindingMap getExchangeBindings(const std::string& ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            auto eit = _bindings.find(ename);
            if (eit == _bindings.end()) return MsgQueueBindingMap();
            return eit->second;
        }

        // test
        Binding::ptr getBinding(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            auto eit = _bindings.find(ename);
            if (eit == _bindings.end()) return Binding::ptr(); // 没有交换机相关绑定信息
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end()) return Binding::ptr(); // 交换机没有队列的绑定信息

            return qit->second;
        }

        bool exists(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            auto eit = _bindings.find(ename);
            if (eit == _bindings.end()) return false; // 没有交换机相关绑定信息
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end()) return false; // 交换机没有队列的绑定信息

            return true;
        }

        size_t size()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            
            size_t sz = 0;
            for (auto it = _bindings.begin(); it != _bindings.end(); ++it)
                sz += it->second.size();
            
            return sz;
        }

        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);

            _mapper.removeTable();
            _bindings.clear();
        }

    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;
    };
}
#endif